package butines.core;

import java.awt.AWTException;
import java.awt.Component;
import java.awt.Point;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.SwingUtilities;



/**
 * The InputManager manages input of key and mouse events. Events are mapped to
 * GameActions.
 */
public class InputManager implements InputCodes, Mouse, KeyListener, MouseListener, MouseMotionListener, MouseWheelListener {

	private static final int NUM_MOUSE_CODES = 9;

	// key codes are defined in java.awt.KeyEvent.
	// most of the codes (except for some rare ones like
	// "alt graph") are less than 600.
	private static final int NUM_KEY_CODES = 600;

	private GameAction[] keyActions = new GameAction[NUM_KEY_CODES];
	private GameAction[] mouseActions = new GameAction[NUM_MOUSE_CODES];
	
	private Component component;
	private int mouseX, mouseY;
	private Point mouseLocation;
	private Point centerLocation;
	private Robot robot;
	private boolean isRecentering;
	private boolean relativeMouseMode;

	/**
	 * Creates a new InputManager that listens to input from the specified
	 * component.
	 */
	public InputManager(Component comp) {
		component = comp;
		component.addKeyListener(this);
		component.addMouseListener(this);
		component.addMouseMotionListener(this);
		component.addMouseWheelListener(this);
		component.setFocusTraversalKeysEnabled(false);
		
		mouseLocation = new Point();
		centerLocation = new Point();
		
		try {
			robot = new Robot();
		} catch (AWTException e) {
			robot = null;
		}
	}
	
	public void shutdown() {
	    component.removeKeyListener(this);
        component.removeMouseListener(this);
        component.removeMouseMotionListener(this);
        component.removeMouseWheelListener(this);
        component.setFocusTraversalKeysEnabled(true);
	}

	/**
	 * Sets whether realtive mouse mode is on or not. For relative mouse mode,
	 * the mouse is "locked" in the center of the screen, and only the changed
	 * in mouse movement is measured. In normal mode, the mouse is free to move
	 * about the screen.
	 */
	public void setRelativeMouseMode(boolean mode) {
		relativeMouseMode = mode;
	}

	/**
	 * Returns whether or not relative mouse mode is on.
	 */
	public boolean isRelativeMouseMode() {
		return relativeMouseMode;
	}
	
	/**
	 * Maps a GameAction to a specific key. The key codes are defined in
	 * java.awt.KeyEvent. If the key already has a GameAction mapped to it, the
	 * new GameAction overwrites it.
	 */
	public void defineActionKey(GameAction gameAction, int keyCode) {
		keyActions[keyCode] = gameAction;
	}

	/**
	 * Maps a GameAction to a specific mouse action. The mouse codes are defined
	 * herer in InputManager (MOUSE_MOVE_LEFT, MOUSE_BUTTON_1, etc). If the
	 * mouse action already has a GameAction mapped to it, the new GameAction
	 * overwrites it.
	 */
	public void defineActionMouse(GameAction gameAction, int mouseCode) {
		mouseActions[mouseCode] = gameAction;
	}

	/**
	 * Clears all mapped keys and mouse actions to this GameAction.
	 */
	public void removeAction(GameAction gameAction) {
		for (int i = 0; i < keyActions.length; i++) {
			if (keyActions[i] == gameAction) {
				keyActions[i] = null;
			}
		}

		for (int i = 0; i < mouseActions.length; i++) {
			if (mouseActions[i] == gameAction) {
				mouseActions[i] = null;
			}
		}

		gameAction.reset();
	}

	/**
	 * Gets a List of names of the keys and mouse actions mapped to this
	 * GameAction. Each entry in the List is a String.
	 */
	public List<String> getMaps(GameAction gameCode) {
		ArrayList<String> list = new ArrayList<String>();

		for (int i = 0; i < keyActions.length; i++) {
			if (keyActions[i] == gameCode) {
				list.add(getKeyName(i));
			}
		}

		for (int i = 0; i < mouseActions.length; i++) {
			if (mouseActions[i] == gameCode) {
				list.add(getMouseName(i));
			}
		}
		return list;
	}

	/**
	 * Resets all GameActions so they appear like they haven't been pressed.
	 */
	public void resetAllGameActions() {
		for (int i = 0; i < keyActions.length; i++) {
			if (keyActions[i] != null) {
				keyActions[i].reset();
			}
		}

		for (int i = 0; i < mouseActions.length; i++) {
			if (mouseActions[i] != null) {
				mouseActions[i].reset();
			}
		}
	}

	/**
	 * Gets the x position of the mouse.
	 */
	public int getMouseX() {
		return mouseX;
	}

	/**
	 * Gets the y position of the mouse.
	 */
	public int getMouseY() {
		return mouseY;
	}

	/**
	 * Uses the Robot class to try to postion the mouse in the center of the
	 * screen.
	 * <p>
	 * Note that use of the Robot class may not be available on all platforms.
	 */
	private synchronized void recenterMouse() {
		if (robot != null && component.isShowing()) {
			centerLocation.x = component.getWidth() / 2;
			centerLocation.y = component.getHeight() / 2;
			SwingUtilities.convertPointToScreen(centerLocation, component);
			isRecentering = true;
			robot.mouseMove(centerLocation.x, centerLocation.y);
		}
	}

	public void requestFocus() {
	    // TODO: Melhora isso!
		if (component.hasFocus() == false) {
			if (relativeMouseMode) {
			    component.setVisible(true);
			    component.requestFocus();
			}
		}
	}
	
	private GameAction getKeyAction(KeyEvent e) {
		int keyCode = e.getKeyCode();
		return (keyCode < keyActions.length) ? keyActions[keyCode] : null;
	}

	private GameAction getMouseButtonAction(MouseEvent e) {
		int mouseCode = getMouseButtonCode(e);
		return (mouseCode != -1) ? mouseActions[mouseCode] : null;
	}

	// implement KeyListener
	public void keyPressed(KeyEvent ke) {
		GameAction gameAction = getKeyAction(ke);
		if (gameAction != null) {
			gameAction.press();
		}
		ke.consume();
	}

	public void keyReleased(KeyEvent ke) {
		GameAction gameAction = getKeyAction(ke);
		if (gameAction != null) {
			gameAction.release();
		}
		ke.consume();
	}
	
	public void keyTyped(KeyEvent ke) {
		ke.consume();
	}

	// implement MouseListener
	public void mousePressed(MouseEvent me) {
		GameAction gameAction = getMouseButtonAction(me);
		if (gameAction != null) {
			gameAction.press();
		}
		
        if (me.getButton() == MouseEvent.BUTTON1) {
            button1 = true;
        }
        if (me.getButton() == MouseEvent.BUTTON2) {
            button2 = true;
        }
        if (me.getButton() == MouseEvent.BUTTON3) {
            button3 = true;
        }
        shift = me.isShiftDown();
        ctrl = me.isControlDown();
        alt = me.isAltDown();
	}

	public void mouseReleased(MouseEvent me) {
		GameAction gameAction = getMouseButtonAction(me);
		if (gameAction != null) {
			gameAction.release();
		}

        if (me.getButton() == MouseEvent.BUTTON1) {
            button1 = false;
        }
        if (me.getButton() == MouseEvent.BUTTON2) {
            button2 = false;
        }
        if (me.getButton() == MouseEvent.BUTTON3) {
            button3 = false;
        }
        shift = me.isShiftDown();
        ctrl = me.isControlDown();
        alt = me.isAltDown();
	}
	
	public void mouseClicked(MouseEvent e) {
		// do nothing
	}

	public void mouseEntered(MouseEvent e) {
		mouseMoved(e);
	}

	public void mouseExited(MouseEvent e) {
		mouseMoved(e);
	}
	
	private void mouseHelper(int codeNeg, int codePos, int amount) {
		GameAction gameAction;
		if (amount < 0) {
			gameAction = mouseActions[codeNeg];
		} else {
			gameAction = mouseActions[codePos];
		}
		if (gameAction != null) {
			gameAction.press(Math.abs(amount));
			gameAction.release();
		}
	}
	
	// implement MouseMotionListener
	public void mouseDragged(MouseEvent e) {
		mouseMoved(e);
	}
	
	public synchronized void mouseMoved(MouseEvent me) {
		// this event is from re-centering the mouse - ignore it
		if (isRecentering) {
			isRecentering = false;
		} else {
			int dx = me.getX() - mouseLocation.x;
			int dy = me.getY() - mouseLocation.y;
			mouseHelper(MOUSE_MOVE_LEFT, MOUSE_MOVE_RIGHT, dx);
			mouseHelper(MOUSE_MOVE_UP, MOUSE_MOVE_DOWN, dy);
			if (isRelativeMouseMode()) {
				recenterMouse();
				mouseX += dx;
				mouseY += dy;
			} else {
				mouseX = me.getX();
				mouseY = me.getY();
			}
			mouseX = Math.max(0, Math.min(component.getWidth() - 1, mouseX));
			mouseY = Math.max(0, Math.min(component.getHeight() - 1, mouseY));
		}
		mouseLocation.x = me.getX();
		mouseLocation.y = me.getY();
        shift = me.isShiftDown();
        ctrl = me.isControlDown();
        alt = me.isAltDown();
	}
	
	// implement MouseWheelListener
	public void mouseWheelMoved(MouseWheelEvent mwe) {
		mouseHelper(MOUSE_WHEEL_UP, MOUSE_WHEEL_DOWN, mwe.getWheelRotation());
	}
	
	/**
	 * Gets the name of a key code.
	 */
	public static String getKeyName(int keyCode) {
		return KeyEvent.getKeyText(keyCode);
	}

	/**
	 * Gets the mouse code for the button specified in this MouseEvent.
	 */
	public static int getMouseButtonCode(MouseEvent e) {
		switch (e.getButton()) {
			case MouseEvent.BUTTON1:
				return MOUSE_BUTTON_1;
			case MouseEvent.BUTTON2:
				return MOUSE_BUTTON_2;
			case MouseEvent.BUTTON3:
				return MOUSE_BUTTON_3;
			default:
				return -1;
		}
	}
	
	/**
	 * Gets the name of a mouse code.
	 */
	public static String getMouseName(int mouseCode) {
		switch (mouseCode) {
			case MOUSE_MOVE_LEFT:
				return "Mouse Left";
			case MOUSE_MOVE_RIGHT:
				return "Mouse Right";
			case MOUSE_MOVE_UP:
				return "Mouse Up";
			case MOUSE_MOVE_DOWN:
				return "Mouse Down";
			case MOUSE_WHEEL_UP:
				return "Mouse Wheel Up";
			case MOUSE_WHEEL_DOWN:
				return "Mouse Wheel Down";
			case MOUSE_BUTTON_1:
				return "Mouse Button 1";
			case MOUSE_BUTTON_2:
				return "Mouse Button 2";
			case MOUSE_BUTTON_3:
				return "Mouse Button 3";
			default:
				return "Unknown mouse code " + mouseCode;
		}
	}

	public void resetAll() {
		for (int i = 0; i < keyActions.length; i++) {
			if (keyActions[i] != null) {
				keyActions[i].reset();
			}
		}
		for (int i = 0; i < mouseActions.length; i++) {
			if (keyActions[i] != null) {
				keyActions[i].reset();
			}
		}
	}

    private boolean button1;
    private boolean button2;
    private boolean button3;
    private boolean shift;
    private boolean ctrl;
    private boolean alt;
    

    // Mouse
    public boolean isButton1() {
        return button1;
    }

    public boolean isButton2() {
        return button2;
    }

    public boolean isButton3() {
        return button3;
    }
    
    public boolean isShift() {
    	return shift;
    }
    
    public boolean isCtrl() {
    	return ctrl;
    }
    
    public boolean isAlt() {
    	return alt;
    }
	
}
